package com.yanboo.generator.config;

import com.yanboo.generator.service.DbDriverService;
import com.yanboo.generator.service.DriverLocalStorageService;
import com.yanboo.generator.service.ProjectInfoService;
import com.yanboo.generator.service.dto.DbDriverDto;
import com.yanboo.generator.service.dto.ProjectInfoDto;
import com.yanboo.generator.service.dto.ProjectInfoQueryCriteria;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

@Component
public class DefaultDataSourceFactory implements DataSourceFactory {


    @Autowired
    @Lazy
    private DbDriverService dbDriverService;

    @Autowired
    @Lazy
    private DriverLocalStorageService driverLocalStorageService;

    @Autowired
    @Lazy
    private ProjectInfoService projectInfoService;

    @Autowired
    private ApplicationContext applicationContext;


    /**
     * 创建默认数据源
     */
    @Override
    public DataSource createDefaultDataSource() {
        return applicationContext.getAutowireCapableBeanFactory().getBean(DataSource.class);
    }

    /**
     * 根据所给key返回datasource
     * <p>
     * 此处key和数据源一一对应
     *
     * @param key
     */
    @Override
    public DataSource createDataSource(String key) {
        //设置回默认数据源
        DataSourceKeyHolder.clear();
        HikariDataSource hikariDataSource;
        try {
            ProjectInfoDto projectInfoDto = projectInfoService.queryAll(new ProjectInfoQueryCriteria().setCode(key)).stream().findFirst().orElse(null);

            if (projectInfoDto != null && projectInfoDto.getDbDriver() != null) {
                DbDriverDto dbDriverDto = dbDriverService.findById(projectInfoDto.getDbDriver().getId());
                hikariDataSource = getHikariDataSource(projectInfoDto, dbDriverDto);
            } else {
                hikariDataSource = null;
            }
            if (hikariDataSource==null){
                return null;
            }
            LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
            localContainerEntityManagerFactoryBean.setDataSource(hikariDataSource);
            //((DefaultListableBeanFactory)applicationContext.getAutowireCapableBeanFactory()).registerSingleton(projectInfoDto.getCode()+"_dataSource",dataSource);
            ((DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory()).registerBeanDefinition(projectInfoDto.getCode() + "_entityManagerFactory", BeanDefinitionBuilder.genericBeanDefinition(LocalContainerEntityManagerFactoryBean.class, () -> {
                LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
                entityManagerFactoryBean.setDataSource(hikariDataSource);
                entityManagerFactoryBean.setPackagesToScan("com.yanboo.*");
                HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
                jpaVendorAdapter.setDatabase(Database.MYSQL);
                jpaVendorAdapter.setShowSql(true);
                entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
                entityManagerFactoryBean.setPersistenceUnitName(projectInfoDto.getCode() + "_persistenceUnitName");
                return entityManagerFactoryBean;
            }).getBeanDefinition());
            JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
            jpaTransactionManager.setDataSource(hikariDataSource);
            BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(JpaTransactionManager.class);
            beanDefinitionBuilder.addConstructorArgReference(projectInfoDto.getCode() + "_entityManagerFactory");
            ((DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory()).registerBeanDefinition(projectInfoDto.getCode() + "_jpaTransactionManager", beanDefinitionBuilder.getBeanDefinition());

        } finally {
            //设置为目标数据源
            DataSourceKeyHolder.set(key);
        }
        return hikariDataSource;
    }

    /**
     * 标识key字段名称
     * 用于从bean,map,collection中获取判别dataSource的key
     *
     * @return
     */
    @Override
    public String key() {
        return null;
    }

    private HikariDataSource getHikariDataSource(ProjectInfoDto projectInfoDto, DbDriverDto dbDriverDto) {
        String driverClass = dbDriverDto.getDriverClass();
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            //
            URLClassLoader urlClassLoader = null;
            try {
                urlClassLoader = new URLClassLoader(new URL[]{new URL(dbDriverDto.getDriverPath())});
            } catch (MalformedURLException ex) {
                throw new RuntimeException(ex);
            }
            try {
                urlClassLoader.loadClass(driverClass);
            } catch (ClassNotFoundException ex) {
                throw new RuntimeException(ex);
            }
        }
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl(projectInfoDto.getJdbcUrl());
        config.setDriverClassName(driverClass);
        config.setUsername(projectInfoDto.getUsername());
        config.setPassword(projectInfoDto.getPassword());
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.setPoolName(projectInfoDto.getCode());
        return new HikariDataSource(config);
    }
}
